/**
 * @file   MiraVector2D.cpp
 * @author Josep Sabater (jsabaterm@el.ub.edu)
 * @date   September, 2015
 * @version 1.0.0
 * @ingroup MiraGeometryLib
 * @brief  Implementation of a vector of 2 dimensions.
 *
 *
 */

#include <cmath>
#include <cassert>
#include <algorithm>
#include <iostream>

#include "MiraVector2D.h"
#include "MiraMath.h"

using namespace MiraUtilUBLib;

namespace MiraGeometryLib{

    const int Vector2D::zeroRoundDigits_ = 6;
    const double Vector2D::errorCheck_ = 1e-6;
    
    Vector2D::Vector2D(const std::vector<double> &coordCont) {
        assert(coordCont.size() > 1);
        x_=coordCont[0];
        y_=coordCont[1];        
    }  

    Vector2D::~Vector2D() = default;

    Vector2D& Vector2D::operator = (Vector2D v) {
        swap_(v);
        return *this;
    }

    bool Vector2D::operator == (const Vector2D& v) const {
        return (x_==v.x_ && y_==v.y_);
    }

    bool Vector2D::operator != (const Vector2D& v) const {
        return (x_!=v.x_ || y_!=v.y_);
    }

    Vector2D Vector2D::operator - () const{return Vector2D(-x_, -y_);}

    Vector2D& Vector2D::operator += (const Vector2D& v){
        x_ += v.x_;
        y_ += v.y_;
        return *this;
    }

    Vector2D& Vector2D::operator -= (const Vector2D& v){
        x_ -= v.x_;
        y_ -= v.y_;
        return *this;
    }

    Vector2D& Vector2D::operator *= (const Vector2D& v){
        x_ *= v.x_;
        y_ *= v.y_;
        return *this;
    }

    Vector2D& Vector2D::operator /= (const Vector2D& v){
        x_ /= v.x_;
        y_ /= v.y_;
        return *this;
    }

    Vector2D Vector2D::operator + (const Vector2D& v) const {
        return Vector2D(x_+v.x_, y_+v.y_);
    }

    Vector2D Vector2D::operator - (const Vector2D& v) const {
        return Vector2D(x_-v.x_, y_-v.y_);
    }

    Vector2D Vector2D::operator * (const Vector2D& v) const {
        return Vector2D(x_*v.x_, y_*v.y_);
    }

    Vector2D Vector2D::operator / (const Vector2D& v) const {
        return Vector2D(x_/v.x_, y_/v.y_);
    }

    Vector2D& Vector2D::operator *= (const double& s){
        x_ *= s;
        y_ *= s;
        return *this;
    }

    Vector2D& Vector2D::operator /= (const double& s){
        x_ /= s;
        y_ /= s;
        return *this;
    }

    Vector2D Vector2D::operator * (const double& s) const {
        return Vector2D(x_*s, y_*s);
    }

    Vector2D Vector2D::operator / (const double& s) const {
        return Vector2D(x_/s, y_/s);
    }

    /** Non-member friend to allow left mutiplication
     */
    Vector2D operator * (const double& s, const Vector2D& v) {return v*s;}

    /** Non-member friend function for << operator
     */
    std::ostream& operator << (std::ostream& os, const Vector2D& v){
        return os << "Vector2D (" << v.x_ << "," << v.y_ <<")";
    }

    double Vector2D::dot (const Vector2D& v) const { return(x_*v.x_ + y_*v.y_); }
    double Vector2D::cross (const Vector2D& v) const  { return(x_*v.y_ - y_*v.x_); }
    double Vector2D::squaredNorm() const { return (x_*x_ + y_*y_); }
    double Vector2D::norm() const { return std::sqrt( squaredNorm() ); }
    Vector2D Vector2D::unit() const { return *this/norm(); }

    double Vector2D::positionLine(const Vector2D& va, const Vector2D& vb){
        Vector2D u(va.x()-x_, va.y()-y_);
        Vector2D v(vb.x()-x_, vb.y()-y_);
        return u.cross(v);
    }
    double Vector2D::angle() const { return std::atan2(y_, x_); }
    double Vector2D::angle(const Vector2D &v) const {
        double vCos = dot(v) / (norm() * v.norm());
        double diff1 = Math::diff(1, vCos);
        double diff0 = Math::diff(0, vCos);
        if ( Math::round(diff1, Vector2D::zeroRoundDigits_) == 0. ){
            vCos = 1.;
        }
        else if ( Math::round(diff0, Vector2D::zeroRoundDigits_) == 0. ){
            vCos = 0.;
        }
        return acos(vCos);
    }
    double Vector2D::angleWithSign(const Vector2D& v) const {
        return ( std::atan2(v.y_, v.x_) - std::atan2(y_, x_) );
    }

    bool Vector2D::isZero() const {
        double zero = sqrt(pow(x_, 2) + pow(y_, 2));
        return (Math::round(zero, Vector2D::zeroRoundDigits_) == 0.);
    }

    Vector2D Vector2D::rotate(const double& rad) const {
        double x = std::cos(rad)*x_ - std::sin(rad)*y_;
        double y = std::sin(rad)*x_ + std::cos(rad)*y_;
        return Vector2D(x, y);
    }

    Vector2D Vector2D::translate(const double& x, const double& y) const {
        return Vector2D(x_+x, y_+y);
    }

    Vector2D Vector2D::translate(const Vector2D& v) const {
        return Vector2D(x_+v.x(), y_+v.y());
    }

    Vector2D Vector2D::perpendicular(const Orientation& o) const {
        if (o == Orientation::AntiClockWise){
            return Vector2D(-y_, x_);
        }
        else{
            return Vector2D(y_, -x_);
        }
    }

    Vector2D Vector2D::transform(const AffineTrans2D& aff) const {
        Point2DHom p = aff.transform( Point2DHom(x_, y_) );
        return Vector2D( p.x(), p.y() );
    }

    bool Vector2D::close(const Vector2D& vect) const {
        return close(vect, Vector2D::errorCheck_);
    }

    bool Vector2D::close(const Vector2D& vect, const double& error) const {
        bool xErr = Math::errorGreater(x_, vect.x(), error);
        bool yErr = Math::errorGreater(y_, vect.y(), error);
        return (xErr || yErr) ? false : true;
    }

    void Vector2D::swap_(Vector2D& v) {
        std::swap(x_, v.x_);
        std::swap(y_, v.y_);
    }
}
